import { computed, defineComponent, getCurrentInstance, ImgHTMLAttributes, onBeforeUnmount, onMounted, PropType, ref, VNodeChild, watch } from 'vue'
import { ObjectFitProperty } from 'csstype'
import { findDOMNode, getComponent } from '../_util/props-util'
import { formatLength } from '../_util/css'
import { getScrollParent } from '../_util/Dom/getScrollParent'

type ImageStatus = 'normal' | 'error' | 'loading'

const preCls = 'x-image'

export default defineComponent({
  name: preCls,
  props: {
    src: String,
    width: [String, Number],
    height: [String, Number],
    fit: String as PropType<ObjectFitProperty>,
    alt: String,
    placeholder: [Boolean, Object, String] as PropType<VNodeChild>,
    fallback: String,
    lazy: Boolean,
  },
  emits: ['load', 'error'],
  setup(props, { emit, slots }) {
    const { proxy: vm } = getCurrentInstance()

    const hasPlaceholder = computed(() => !!props.placeholder || !!slots.placeholder)

    const statusRef = ref<ImageStatus>()
    watch(() => props.src, () => {
      statusRef.value = hasPlaceholder.value ? 'loading' : 'normal'
    }, { immediate: true })


    // ========================== Lazy ==========================
    const showRef = ref(!props.lazy)
    let io: IntersectionObserver
    onMounted(() => {
      if (!props.lazy) return
      const el = findDOMNode(vm)
      io = new IntersectionObserver(([entry]) => {
        if (!entry.isIntersecting) return
        showRef.value = true
        io.disconnect()
        io = undefined
      }, {
        root: getScrollParent(el)
      })
      io.observe(el)
    })
    onBeforeUnmount(() => {
      io?.disconnect()
    })


    // ========================== Event ==========================
    function onLoad(e) {
      statusRef.value = 'normal'
      emit('load', e)
    }

    function onError(e) {
      statusRef.value = 'error'
      emit('error', e)
    }


    // ========================== Element ==========================
    function getImg() {
      if (!showRef.value) return
      const imgProps: ImgHTMLAttributes = {
        class: `${preCls}_img`,
        style: {
          objectFit: props.fit
        },
        alt: props.alt,
        src: props.src,
        onLoad,
        onError
      }
      return <img {...imgProps} />
    }

    function getPlaceholder() {
      if (!hasPlaceholder.value) return
      return (
        <div class={`${preCls}_placeholder`}>
          { props.placeholder !== true ? getComponent(vm, 'placeholder') : undefined || '加载中……' }
        </div>
      )
    }

    function getError() {
      if (props.fallback) {
        return <img class={`${preCls}_error`} src={props.fallback} />
      } else {
        return <div class={`${preCls}_error`}>{ slots.error?.() || '┗( T﹏T )┛' }</div>
      }
    }


    return () => {
      const status = statusRef.value

      const style = {
        width: formatLength(props.width),
        height: formatLength(props.height)
      }

      return (
        <div class={preCls} style={style}>
          { status !== 'error' && getImg() }
          {
            status === 'loading'
              ? getPlaceholder()
              : status === 'error'
                ? getError() : undefined
          }
        </div>
      )
    }
  }
})